﻿using Mina.Core.Buffer;
using Mina.Filter.Codec;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;

namespace SharpIM.Http
{
    public class HttpResponse
    {
        public HttpContext HttpContext { get; set; }
        public string ContentCharset { get; set; }
        public string ContentEncoding { get; set; }
        public void SetCookie(string name, string value)
        {
            if (Regex.IsMatch(this.HttpContext.Request.Domain, "^(?=^.{3,255}$)[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$"))
            {
                SetCookie(name, value, "." + this.HttpContext.Request.Domain);
            }
            else
            {
                SetCookie(name, value, this.HttpContext.Request.Domain);
            }
        }
        public void SetCookie(string name, string value, string domain)
        {
            SetCookie(name, value, domain, "/");
        }
        public void SetCookie(Cookie cookie)
        {
            this.Cookies.Remove(cookie.Name);
            this.Cookies.Add(cookie.Name, cookie);
        }
        public void SetCookie(string name, string value, string domain, string path)
        {
            this.Cookies.Remove(name);
            this.Cookies.Add(name, new Cookie(name, value, path, domain));
        }
        public Dictionary<string, Cookie> Cookies { get; set; }


        public const string Server = "mina.net http server 1.0";
        const string Version = "HTTP/1.1";
        public int StatusCode { get; set; }
        string statusCodeDescription;
        public string StatusCodeDescription
        {
            get
            {
                if (statusCodeDescription != null)
                {
                    return statusCodeDescription;
                }
                else if (statusCodeDescriptions.ContainsKey(this.StatusCode))
                {
                    return statusCodeDescriptions[StatusCode];
                }
                else
                {
                    return "-";
                }
            }
            set
            {
                statusCodeDescription = value;
            }
        }
        internal HttpResponse(HttpContext httpContext)
        {
            this.HttpContext = httpContext;
            this.StatusCode = 200;
            Cookies = new Dictionary<string, Cookie>();
            Headers = new Dictionary<string, string>();
            Headers.Add("Server", Server);
            Headers.Add("Date", DateTime.Now.ToString("r"));
            this.ContentType = "text/html; charset=utf-8";
            this.ContentLength = 0;
        }
        public IHttpResult Body { get; set; }

        public string ContentType
        {
            get
            {
                return this.GetHeader("Content-Type", "text/html");
            }
            set
            {
                this.SetHeader("Content-Type", value);
            }
        }
        public int ContentLength
        {
            get
            {
                return this.GetHeaderAsInt("Content-Length", 0);
            }
            set
            {
                this.SetHeader("Content-Length", value);
            }
        }
        public Dictionary<string, string> Headers { get; private set; }
        const string respTpl = Version + " {0} {1} \r\n{2}\r\n\r\n";
        string _rawHeader;
        public string RawHeader
        {
            get
            {
                return _rawHeader ?? (_rawHeader = string.Format(respTpl, StatusCode, StatusCodeDescription, GetHeaderEntityString()));
            }
        }

        public Exception Error { get; internal set; }

        string GetHeaderEntityString()
        {
            return string.Join("\r\n", Headers.Select(x => x.Key + ": " + x.Value).Concat(GetSetCookieEntityString()));
        }

        string[] GetSetCookieEntityString()
        {
            List<string> values = new List<string>();
            foreach (var item in Cookies.Values)
            {
                var cookieStr = $"Set-Cookie: {item.Name}={item.Value}; path={item.Path}; domain={item.Domain};";
                if (item.Expires != DateTime.MinValue)
                {
                    cookieStr += (" " + item.Expires.ToString("r") + ";");
                }
                values.Add(cookieStr);
            }
            return values.ToArray();
        }

        public int GetHeaderAsInt(string key, int defaultValue)
        {
            int value;
            if (Headers.ContainsKey(key) && int.TryParse(Headers[key], out value))
            {
                return value;
            }
            return defaultValue;
        }
        public string GetHeader(string key, string defaultValue)
        {
            if (Headers.ContainsKey(key) && Headers[key] != null)
            {
                return Headers[key];
            }
            return defaultValue;
        }
        public DateTime GetHeaderAsDateTime(string key, DateTime defaultValue)
        {
            DateTime value;
            if (Headers.ContainsKey(key) && DateTime.TryParse(Headers[key], out value))
            {
                return value;
            }
            return defaultValue;
        }
        public void SetHeader(string name, DateTime value)
        {
            this.SetHeader(name, value.ToString());
        }
        public void SetHeader(string name, int value)
        {
            this.SetHeader(name, value.ToString());
        }
        public void SetHeader(string name, string value)
        {
            this.Headers.Remove(name);
            this.Headers.Add(name, value);
        }
        public static Dictionary<int, string> statusCodeDescriptions = new Dictionary<int, string>();
        internal ResponseStream Output = null;
        internal void OutputResponse(IProtocolEncoderOutput output)
        {
            if (Body != null)
            {
                Output = this.Body.ToResponseStream();
                Output.EnsureValid();
                this.ContentLength = (int)Output.Length;
                if (!string.IsNullOrEmpty(this.Body.ContentType))
                    this.ContentType = this.Body.ContentType;
            }
            OutputHead(output);
            if (Output != null && Output.Length > 0)
            {
                int size = 0;
                var buffer = new byte[4096];
                while ((size = Output.Read(buffer, 0, buffer.Length)) > 0)
                {
                    var iob = IoBuffer.Wrap(buffer, 0, size);
                    output.Write(iob);
                }
                Output.Dispose();
            }
        }
        void OutputHead(IProtocolEncoderOutput output)
        {
            IoBuffer iob = IoBuffer.Wrap(Encoding.UTF8.GetBytes(RawHeader));
            output.Write(iob);
        }

        static HttpResponse()
        {
            statusCodeDescriptions.Add(200, "OK");// 请求成功。一般用于GET与POST请求
            statusCodeDescriptions.Add(201, "Created");// 已创建。成功请求并创建了新的资源
            statusCodeDescriptions.Add(202, "Accepted");// 已接受。已经接受请求，但未处理完成
            statusCodeDescriptions.Add(203, "Non - Authoritative Information");// 非授权信息。请求成功。但返回的meta信息不在原始的服务器，而是一个副本
            statusCodeDescriptions.Add(204, "No Content");//  无内容。服务器成功处理，但未返回内容。在未更新网页的情况下，可确保浏览器继续显示当前文档
            statusCodeDescriptions.Add(205, "Reset Content");//   重置内容。服务器处理成功，用户终端（例如：浏览器）应重置文档视图。可通过此返回码清除浏览器的表单域
            statusCodeDescriptions.Add(206, "Partial Content");// 部分内容。服务器成功处理了部分GET请求
            statusCodeDescriptions.Add(300, "Multiple Choices");//    多种选择。请求的资源可包括多个位置，相应可返回一个资源特征与地址的列表用于用户终端（例如：浏览器）选择
            statusCodeDescriptions.Add(301, "Moved Permanently");//   永久移动。请求的资源已被永久的移动到新URI，返回信息会包括新的URI，浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替
            statusCodeDescriptions.Add(302, "Found");// 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
            statusCodeDescriptions.Add(303, "See Other");//   查看其它地址。与301类似。使用GET和POST请求查看
            statusCodeDescriptions.Add(304, "Not Modified");//    未修改。所请求的资源未修改，服务器返回此状态码时，不会返回任何资源。客户端通常会缓存访问过的资源，通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源
            statusCodeDescriptions.Add(305, "Use Proxy");//   使用代理。所请求的资源必须通过代理访问
            statusCodeDescriptions.Add(306, "Unused");// 已经被废弃的HTTP状态码
            statusCodeDescriptions.Add(307, "Temporary Redirect");//  临时重定向。与302类似。使用GET请求重定向
            statusCodeDescriptions.Add(400, "Bad Request");// 客户端请求的语法错误，服务器无法理解
            statusCodeDescriptions.Add(401, "Unauthorized");// 请求要求用户的身份认证
            statusCodeDescriptions.Add(402, "Payment Required");//    保留，将来使用
            statusCodeDescriptions.Add(403, "Forbidden");// 服务器理解请求客户端的请求，但是拒绝执行此请求
            statusCodeDescriptions.Add(404, "Not Found");//   服务器无法根据客户端的请求找到资源（网页）。通过此代码，网站设计人员可设置"您所请求的资源无法找到"的个性页面
            statusCodeDescriptions.Add(405, "Method Not Allowed");// 客户端请求中的方法被禁止
            statusCodeDescriptions.Add(406, "Not Acceptable");//  服务器无法根据客户端请求的内容特性完成请求
            statusCodeDescriptions.Add(407, "Proxy Authentication Required");// 请求要求代理的身份认证，与401类似，但请求者应当使用代理进行授权
            statusCodeDescriptions.Add(408, "Request Time-out");//	服务器等待客户端发送的请求时间过长，超时
            statusCodeDescriptions.Add(409, "Conflict");// 服务器完成客户端的PUT请求是可能返回此代码，服务器处理请求时发生了冲突
            statusCodeDescriptions.Add(410, "Gone");// 客户端请求的资源已经不存在。410不同于404，如果资源以前有现在被永久删除了可使用410代码，网站设计人员可通过301代码指定资源的新位置
            statusCodeDescriptions.Add(411, "Length Required");// 服务器无法处理客户端发送的不带Content - Length的请求信息
            statusCodeDescriptions.Add(412, "Precondition Failed");// 客户端请求信息的先决条件错误
            statusCodeDescriptions.Add(413, "Request Entity Too Large");//    由于请求的实体过大，服务器无法处理，因此拒绝请求。为防止客户端的连续请求，服务器可能会关闭连接。如果只是服务器暂时无法处理，则会包含一个Retry - After的响应信息
            statusCodeDescriptions.Add(414, "Request - URI Too Large");//   请求的URI过长（URI通常为网址），服务器无法处理
            statusCodeDescriptions.Add(415, "Unsupported Media Type");// 服务器无法处理请求附带的媒体格式
            statusCodeDescriptions.Add(416, "Requested range not satisfiable");// 客户端请求的范围无效
            statusCodeDescriptions.Add(417, "Expectation Failed");//  服务器无法满足Expect的请求头信息
            statusCodeDescriptions.Add(500, "Internal Server Error");// 服务器内部错误，无法完成请求
            statusCodeDescriptions.Add(501, "Not Implemented");// 服务器不支持请求的功能，无法完成请求
            statusCodeDescriptions.Add(502, "Bad Gateway");// 充当网关或代理的服务器，从远端服务器接收到了一个无效的请求
            statusCodeDescriptions.Add(503, "Service Unavailable");// 由于超载或系统维护，服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry - After头信息中
            statusCodeDescriptions.Add(504, "Gateway Time-out");//	充当网关或代理的服务器，未及时从远端服务器获取请求
            statusCodeDescriptions.Add(505, "HTTP Version not supported");// 服务器不支持请求的HTTP协议的版本，无法完成处理
        }
    }
}
